/* ConsoleView.cpp
Michael Zahniser, 01/07/09

Function definitions for the ConsoleView class.
*/

#include "ConsoleView.h"

#include "Interface.h"
#include "EmbeddedFont.h"

#include "_common/shift.h"
#include "_paste/Pasteboard.h"



ConsoleView::ConsoleView()
	: queryIndent(QUERY_INDENT), responseIndent(RESPONSE_INDENT),
	scroll(0), callback(NULL)
{
	queryFont.Load(EmbeddedFont::DATA, EmbeddedFont::WIDTH, EmbeddedFont::HEIGHT);
	responseFont.Load(EmbeddedFont::DATA, EmbeddedFont::WIDTH, EmbeddedFont::HEIGHT);
	
	lineHeight = queryFont.Height() + LINE_HEIGHT_PAD;
	
	NewLine();
}



void ConsoleView::SetCallback(Callback &callback)
{
	this->callback = &callback;
}



void ConsoleView::SetQueryColor(int red, int green, int blue)
{
	queryFont.SetColor(red, green, blue);
}



void ConsoleView::SetResponseColor(int red, int green, int blue)
{
	responseFont.SetColor(red, green, blue);
}



void ConsoleView::SetLineHeight(int height)
{
	lineHeight = height;
}



void ConsoleView::SetQueryIndent(int indent)
{
	queryIndent = indent;
}



void ConsoleView::SetResponseIndent(int indent)
{
	responseIndent = indent;
}



// Display a response in the console.
void ConsoleView::PostResponse(const string &text)
{
	lines.insert(++lines.begin(), Entry(text));

	if(Interface *gui = GetInterface())
		gui->Invalidate();
}



// Initialize a view, given the command line arguments for the program.
// If the view cannot be initialized, this function should return false.
// NOTE: SDL may not be initialized yet when a View's constructor is called,
// so all initialization using SDL should be done here instead.
bool ConsoleView::Initialize(const Surface &surface)
{
	return true;
}



// Handle an event. If the event involves (x, y) coordinates, they have been
// translated such that (0, 0) is the top left corner of this View.
// This function should return false if this view does not support the given
// event and wants other views to have a chance to handle it.
bool ConsoleView::Handle(const SDL_Event &event)
{
	if(event.type == SDL_MOUSEBUTTONDOWN)
	{
		if(scroll && event.button.button == 5)
			--scroll;
		else if(event.button.button == 4)
			++scroll;
	}
	if(event.type == SDL_KEYDOWN)
	{
		scroll = 0;

		SDLKey key = event.key.keysym.sym;
		SDLMod mod = event.key.keysym.mod;

		// Get the modifier key state.
		bool isCommand = (mod & (KMOD_CTRL | KMOD_META)) != 0;
		bool isShifted = (mod & KMOD_SHIFT) != 0;

		// TODO: Define a separate class that does the event handling for editing
		// a text string (moving cursor, cutting and pasting, etc).
		if(key == SDLK_ESCAPE)
		{
			Text().clear();
			if(callback)
				(*callback)(Text());
			if(Interface *gui = GetInterface())
				gui->Remove(this);
		}
		else if(key == SDLK_RETURN)
		{
			const string &text = Text();
			NewLine();
			if(callback && !text.empty())
				(*callback)(text);
		}
		else if(isCommand && key == 'c')
		{
			Pasteboard::Set(Text());
		}
		else if(isCommand && key == 'x')
		{
			Pasteboard::Set(Text());
			Text().clear();
			cursor = 0;
		}
		else if(isCommand && key == 'v')
		{
			string clip = Pasteboard::Get();
			Text().insert(cursor, clip);
			cursor += static_cast<int>(clip.length());
		}
		else if(key == SDLK_LEFT && cursor)
			--cursor;
		else if(key == SDLK_RIGHT && cursor < static_cast<int>(Text().length()))
			++cursor;
		else if(key == SDLK_UP && history != lines.end())
		{
			// For some reason this is necessary, rather than just a shorter,
			// simpler loop without saving these values. Is this a race condition?
			bool isResponse = true;
			bool isEnd = true;
			do {
				++history;
				isEnd = (history == lines.end());
				if(!isEnd)
					isResponse = history->isResponse;
			} while(!isEnd && isResponse);

			if(isEnd)
				Text().clear();
			else
				Text() = history->text;
			cursor = static_cast<int>(Text().length());
		}
		else if(key == SDLK_DOWN && history != lines.begin())
		{
			// For some reason this is necessary, rather than just a shorter,
			// simpler loop without saving these values. Is this a race condition?
			bool isResponse = true;
			bool isBegin = true;
			do {
				--history;
				isBegin = (history == lines.begin());
				if(!isBegin)
					isResponse = history->isResponse;
			} while(!isBegin && isResponse);

			if(isBegin)
				Text().clear();
			else
				Text() = history->text;
			cursor = static_cast<int>(Text().length());
		}
		else if((key == SDLK_DELETE || key == SDLK_BACKSPACE) && cursor)
		{
			--cursor;
			Text().erase(cursor, 1);
		}
		else if(!isCommand && key >= ' ' && key <= '~')
		{
			Text().insert(cursor, 1, isShifted ? SHIFT[key] : key);
			++cursor;
		}

		return true;
	}

	return false;
}



// Draw the view in the given surface. The surface is passed as a (shallow)
// copy so that it is impossible for derived classes to modify View::surface
// to point to anything other than what Interface assigns to it.
void ConsoleView::Draw(Surface surface) const
{
	surface.Erase(surface.Color(255));

	int y = surface.Height() + lineHeight * (scroll - 1);

	int cursorX = queryIndent + queryFont.Width(Text().begin(), Text().begin() + cursor);
	surface.VLine(cursorX, y, lineHeight, surface.Color(128));

	for(list<Entry>::const_iterator it = lines.begin(); it != lines.end() && y > 0; ++it, y -= lineHeight)
	{
		if(y >= surface.Height())
			continue;

		if(it->isResponse)
			responseFont.Draw(it->text, surface, responseIndent, y);
		else
			queryFont.Draw(it->text, surface, queryIndent, y);
	}
}



string &ConsoleView::Text()
{
	return lines.front().text;
}



const string &ConsoleView::Text() const
{
	return lines.front().text;
}



void ConsoleView::NewLine()
{
	lines.push_front(Entry());
	cursor = 0;
	history = lines.begin();
}
